#!/bin/bash
set -e
set -o pipefail

# 获取脚本路径
current_dir=$(cd $(dirname $0); pwd)
# 导入公共方法
source ${current_dir}/common.sh
# 导入配置文件
source ${current_dir}/config.cnf
# 导入 deploy_pg.sh\deploy_backup_pgbackrest.sh ，使用其中的方法
#source ${current_dir}/deploy_pg.sh
#source ${current_dir}/deploy_backup_pgbackrest.sh


# 安装patroni
function patroni_install(){
    if [ $sys_type = "yum" ];then
        yum install -y gcc python-pip python-psycopg2 python-devel python3
        yum install -y patroni-etcd
    else
        apt-get install -y gcc python-pip python-psycopg2 python-devel python3
        apt-get install -y patroni-etcd
    fi
}

# 安装watchdog
function watchdog_install(){
  if [ "watchdog_enable" -eq 1 ];then
    comm_print_note "安装watchdog"
    if [ $sys_type = "yum" ];then
      yum install -y watchdog
    else
      apt-get -y install watchdog
    fi
    modprobe softdog
    chown postgres.postgres /dev/watchdog
    chmod 666 /dev/watchdog
  fi
}

# 获取动态参数值
function pg_get_config_dynamic_parameter(){
    #free_mem=$(free |grep Mem|awk '{print $2}')
    free_mem=${allow_mem_bytes}
    shared_buffers="$(expr ${free_mem} / 1024 / 4)MB"
    maintenance_work_mem="$(expr ${free_mem} / 1024 / 16)MB"
    effective_cache_size="$(expr ${free_mem} / 1024 / 2)MB"
    set +e
    cpu_core=$(expr $(nproc) / 2)
    set -e
    min_wal_size=$(expr ${free_mem} / 8192)
    max_wal_size=$(expr ${free_mem} / 2048)
    autovacuum_work_mem=$(expr ${free_mem} / 64)

    if [ $cpu_core -lt 1 ];then
        max_parallel_maintenance_workers=2
        max_parallel_workers_per_gather=2
    else
        max_parallel_maintenance_workers="$cpu_core"
        max_parallel_workers_per_gather="$cpu_core"
    fi

    if [ ${min_wal_size} -lt 256 ];then
        min_wal_size='256MB'
    elif [ ${min_wal_size} -gt 8192 ];then
        min_wal_size='8GB'
    else
        min_wal_size="${min_wal_size}MB"
    fi

    if [ ${max_wal_size} -lt 2048 ];then
        max_wal_size='2GB'
    elif [ ${max_wal_size} -gt 16384 ];then
        max_wal_size='16GB'
    else
        max_wal_size="${max_wal_size}MB"
    fi

    if [ $autovacuum_work_mem -lt 128 ];then
        autovacuum_work_mem="128MB"
    else
        autovacuum_work_mem="${autovacuum_work_mem}MB"
    fi

    if [ ${is_ssd} -eq 1 ];then
        random_page_cost=1.1
        effective_io_concurrency=200 
    else
        random_page_cost=4
        effective_io_concurrency=2
    fi

    timezone='Asia/Shanghai'
}

# 配置patroni配置文件
function patroni_set_config(){
    
    mkdir -p ${pg_dir}/patroni/{log,bin}
    chown -R postgres.postgres ${pg_dir}/patroni

    #local pg_name="pg$(echo ${local_ip}|awk -F . '{print $4}')"
    local pg_name="${local_ip}"
    local timezone='Asia/Shanghai'
    local array=(${etcd_cluster_ip//,/ })
    cat > /etc/patroni/patroni.yml << EOF
scope: pgsql
namespace: ${pg_dir}/patroni
name: ${pg_name}

log:
  level: INFO                           #  NOTEST|DEBUG|INFO|WARNING|ERROR|CRITICAL
  dir: ${pg_dir}/patroni/log            #  patroni log dir
  file_size: 33554432                   #  32MB log triggers log rotation
  file_num: 10                          #  keep at most 10x32MB = 320M log
  dateformat: '%Y-%m-%d %H:%M:%S %z'    #  IMPORTANT: discard milli timestamp
  format: '%(asctime)s %(levelname)s: %(message)s'

restapi:
  listen: 0.0.0.0:8000
  connect_address: ${local_ip}:8000

etcd3:
  hosts: 
  - ${array[0]}:${ectd_client_port}
  - ${array[1]}:${ectd_client_port}
  - ${array[2]}:${ectd_client_port}

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    master_start_timeout: 300
    primary_start_timeout: 30
    #synchronous_mode: true
    # 参数开启表示必须每次写入能存储在两个节点上，若不写，则会挂起主，主要保证数据必然能写在一个副本上
    ###synchronous_mode_strict: true
    #synchronous_standby_names: "*"
    # 与主不同时间轴的不提升
    check_timeline: true
    # ETCD故障则直接降级为只读
    failsafe_mode: on
    patroni_watchdog_mode: automatic
    postgresql:
      use_pg_rewind: true
      use_slots: true
      parameters:
        listen_addresses: '*'
        port: ${pg_port}
        max_connections: 1024
        superuser_reserved_connections: 10
        track_commit_timestamp: on
        wal_level: logical
        wal_log_hints: on
        wal_keep_segments: 128
        max_wal_senders: 24
        max_replication_slots: 16
        password_encryption: md5
        shared_buffers: ${shared_buffers}
        work_mem: 4MB
        maintenance_work_mem: ${maintenance_work_mem}
        # 根据CPU核数来算，至少值为2   max(CPU核数/2,2)
        max_parallel_maintenance_workers: ${max_parallel_maintenance_workers}
        # 根据CPU核数来算，至少值为2   max(CPU核数/2,2)
        max_parallel_workers_per_gather: ${max_parallel_workers_per_gather}
        #max_parallel_workers: max(DBInstanceClassCPU*3/4, 8)
        max_parallel_workers: 8
        temp_file_limit: 20GB
        #vacuum_cost_delay: 20ms
        #vacuum_cost_limit: 2000
        bgwriter_lru_maxpages: 800
        bgwriter_lru_multiplier: 5.0
        # 最小256M，最大8G
        min_wal_size: ${min_wal_size}
        # 最小2G，最大16G
        max_wal_size: ${max_wal_size}
        wal_buffers: 16MB
        wal_writer_delay: 20ms
        #wal_writer_flush_after: 1MB
        checkpoint_timeout: 15min
        archive_mode: on
        archive_timeout: 300
        archive_command: 'pgbackrest --stanza=${bak_stanza_name} archive-push %p'
        #vacuum_defer_cleanup_age: 0
        hot_standby: on
        max_standby_archive_delay: 10min
        max_standby_streaming_delay: 3min
        wal_receiver_status_interval: 1s
        hot_standby_feedback: on
        #wal_receiver_timeout: 60s
        #max_logical_replication_workers: 8
        # HDD:4   SSD:1.1
        random_page_cost: ${random_page_cost}
        # HDD:2  SSD:200 
        effective_io_concurrency: ${effective_io_concurrency}
        effective_cache_size: ${effective_cache_size}
        
        ## 时区相关
        timezone: '${timezone}'
        log_timezone: '${timezone}'
        
        ## 日志相关配置
        log_destination: csvlog
        logging_collector: on
        log_truncate_on_rotation : on
        log_filename: 'postgresql-%d.log'
        # 当天产生日志超出5G则覆盖重写，也可设置为0不覆盖
        log_rotation_size: 500MB
        log_rotation_age: 1d
        log_checkpoints: on
        log_lock_waits: on
        log_statement: ddl
        log_min_duration_statement: 1000
        
        # 统计信息
        track_io_timing: on
        track_functions: pl
        track_activity_query_size: 4096
        
        # AUTOVACUUM
        log_autovacuum_min_duration: 10s
        autovacuum_work_mem: ${autovacuum_work_mem}
        autovacuum_max_workers: 3
        autovacuum_naptime: 1min
        autovacuum_vacuum_scale_factor: 0.08
        autovacuum_analyze_scale_factor: 0.05
        #autovacuum_vacuum_cost_delay: -1
        #autovacuum_vacuum_cost_limit: -1
        
        idle_in_transaction_session_timeout: 10min
        shared_preload_libraries: 'pg_stat_statements'
        
        #auto_explain.log_min_duration: -1
        #auto_explain.log_analyze: off
        #auto_explain.log_verbose: off
        #auto_explain.log_timing: off
        #auto_explain.log_nested_statements: t
        
        pg_stat_statements.max: 5000
        pg_stat_statements.track: top
        pg_stat_statements.track_utility: off
        pg_stat_statements.track_planning: off

  initdb:
  - encoding: UTF8
  - locale: C
  - lc-ctype: en_US.UTF-8

  pg_hba:
  - local   all             all                                     peer
  - host    all             all             127.0.0.1/32            ident
  - host    all             all             0.0.0.0/0               md5
  - local   replication     all                                     peer
  - host    replication     all             127.0.0.1/32            ident
  - host    replication     all             0.0.0.0/0               md5

postgresql:
  listen: 0.0.0.0:${pg_port}
  connect_address: ${local_ip}:${pg_port}
  data_dir: ${pg_datadir}
  bin_dir: ${pg_home}/bin

  authentication:
    replication:
      username: repl
      password: "${replication_password}"
    superuser:
      username: postgres
      password: "${postgres_password}"
    rewind:
      username: postgres
      password: "${postgres_password}"
    basebackup:
      #max-rate: '100M'
      checkpoint: 'fast'

  #------------------------------------------------------------#
  # how to react to database operations
  #------------------------------------------------------------#
  # event callback script log: /pg/log/patroni/callback.log
  callbacks:
    on_start: ${pg_dir}/patroni/bin/patroni_switch_vip.sh
    on_stop: ${pg_dir}/patroni/bin/patroni_switch_vip.sh
    on_reload: ${pg_dir}/patroni/bin/patroni_switch_vip.sh
    on_restart: ${pg_dir}/patroni/bin/patroni_switch_vip.sh
    on_role_change: ${pg_dir}/patroni/bin/patroni_switch_vip.sh

  # rewind policy: data checksum should be enabled before using rewind
  use_pg_rewind: true
  remove_data_directory_on_rewind_failure: false
  remove_data_directory_on_diverged_timelines: false
  
  create_replica_methods:
    - basebackup
  basebackup:
    - max-rate: '1000M'
    - checkpoint: fast
    - verbose
    - progress
  pgbackrest:
    command: /usr/bin/pgbackrest --stanza=${bak_stanza_name} --delta restore
    keep_data: true
    no_params: true
    no_leader: true
    no_leader: true

tags:
    nofailover: false
    noloadbalance: false
    clonefrom: false
    nosync: false
EOF
}

# 生成vip切换脚本
function patroni_config_switch_vip(){
    local vip=${vip}
    local inf=$(ip addr show |grep ${local_ip}|awk '{print $NF}')

    cat > ${pg_dir}/patroni/bin/patroni_switch_vip.sh << EOF
#!/bin/bash

readonly OPERATION=\$1
readonly ROLE=\$2
readonly SCOPE=\$3

VIP=$vip
INF=$inf

# 检查 VIP 是否已存在[0：不存在 1:已存在]
check_vip_exists(){
  local chk_value=\$(sudo ip addr show dev \${INF} to \${VIP})
  if [ -n "\$chk_value" ];then
    return 1
  else
    return 0
  fi
}

# arping检查 VIP 是否已被占用[0：未占用 1:已占有]
check_vip_arp(){
  sudo arping -c 1 -I \${INF} \${VIP}
  if [ \$? -eq 0 ];then
    return 1
  else
    return 0
  fi
}

# 添加 VIP
cmd_vip_add(){
  if check_vip_exists && check_vip_arp;then
    sudo ip addr add  \${VIP} dev \${INF}
    echo "\$(date "+%Y-%m-%d %H:%M:%S %z") VIP \${VIP} added"  >> ${pg_dir}/patroni/log/patroni.log
  else
    echo "\$(date "+%Y-%m-%d %H:%M:%S %z") VIP \${VIP} already be used,please check!" >> ${pg_dir}/patroni/log/patroni.log
  fi
}

# 删除 VIP
cmd_vip_del(){
  echo "\$(date "+%Y-%m-%d %H:%M:%S %z") 执行删除 VIP 操作" >> /app/pgsql/patroni/log/patroni.log
  if ! check_vip_exists;then
    sudo ip addr del \${VIP}/32 dev \${INF}
    echo "\$(date "+%Y-%m-%d %H:%M:%S %z") VIP \${VIP} removed" >> ${pg_dir}/patroni/log/patroni.log
  else
    echo "\$(date "+%Y-%m-%d %H:%M:%S %z") VIP \${VIP} not exists!" >> ${pg_dir}/patroni/log/patroni.log
  fi
}

# 设置备份相关信息
set_backup_on_master(){
  # 被拉升为主则需要将备份挂载在这台机器上
  echo "\$(date "+%Y-%m-%d %H:%M:%S %z") 首次或切换，配置主实例备份信息..." >> ${pg_dir}/patroni/log/patroni.log
  ${pg_backupdir}/backup_pgbackrest.sh "create" >> ${pg_dir}/patroni/log/patroni.log
  ${pg_backupdir}/backup_pgbackrest.sh "check" >> ${pg_dir}/patroni/log/patroni.log
  ${pg_backupdir}/backup_pgbackrest.sh "add_crontab" >> ${pg_dir}/patroni/log/patroni.log
}

# 删除非主库上的备份定时任务
del_backup_on_slave(){
  
  local chk_value=\$(cat \/etc\/crontab |grep backup_pgbackrest|wc -l)
  if [ \$chk_value -gt 0 ];then
     #echo "\$(date "+%Y-%m-%d %H:%M:%S %z") 删除实例备份crontab配置..." >> ${pg_dir}/patroni/log/patroni.log
     ${pg_backupdir}/backup_pgbackrest.sh "del_crontab" >> ${pg_dir}/patroni/log/patroni.log
  fi
}

echo "\$(date "+%Y-%m-%d %H:%M:%S %z") This is patroni callback \$OPERATION \$ROLE \$SCOPE" >> ${pg_dir}/patroni/log/patroni.log

case \$OPERATION in
 on_stop)
 cmd_vip_del
 del_backup_on_slave
 ;;
 on_reload)
 if [[ \$ROLE == 'master' ]];then
 
   echo "\$(date "+%Y-%m-%d %H:%M:%S %z") 进入on_reload,重启patroni防止拉升为错误的主..." >> /app/pgsql/patroni/log/patroni.log
   systemctl restart patroni
   cmd_vip_add 
   set_backup_on_master
 else
   cmd_vip_del
   del_backup_on_slave
 fi
 ;;
 on_start | on_restart | on_role_change)
 if [[ \$ROLE == 'master' || \$ROLE == 'standby_leader' ]]; then
   cmd_vip_add 
   # 设置主服务器上的备份信息
   set_backup_on_master
 else
   cmd_vip_del
   del_backup_on_slave 
 fi
 ;;
 *)
 usage
 ;;
esac
EOF

    chown postgres.postgres ${pg_dir}/patroni/bin/patroni_switch_vip.sh
    chmod +x ${pg_dir}/patroni/bin/patroni_switch_vip.sh
}

# 配置postgres用户sudo免密
function system_set_postgres_sudo_nopass(){
    local check_value=$(cat /etc/sudoers|grep postgres|grep /usr/sbin/ip|wc -l)
    if [ $check_value -eq 0 ];then
        sed -i '101i postgres ALL=(root) NOPASSWD: /usr/sbin/ip'  /etc/sudoers
        sed -i '102i postgres ALL=(root) NOPASSWD: /usr/sbin/arping'  /etc/sudoers
        sed -i '103i postgres ALL=(root) NOPASSWD: /usr/bin/sed'  /etc/sudoers
        sed -i '104i postgres ALL=(root) NOPASSWD: /bin/tee'  /etc/sudoers
    fi
}

# 配置 patroni 环境变量
function patroni_set_env(){
    local isenv=$(cat /home/postgres/.bash_profile|grep PATRONICTL_CONFIG_FILE|wc -l)
    if [ $isenv -eq 0 ];then
      echo "export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml" >> /home/postgres/.bash_profile
    fi
}

# 打印 patroni 状态
function patroni_status_print(){
    systemctl status patroni
    patronictl -c /etc/patroni/patroni.yml list 
}

function deploy_patroni(){
    comm_print_note "首先安装PG..."
    ${current_dir}/deploy_pg.sh pgonly
    comm_print_note "postgres用户配置免密"
    system_set_postgres_sudo_nopass
    # comm_print_note "安装watchdog"
    watchdog_install
    comm_print_note "安装patroni"
    patroni_install
    comm_print_note "设置patroni配置文件"
    pg_get_config_dynamic_parameter
    patroni_set_config
    comm_print_note "生成patroni使用的VIP切换脚本"
    patroni_config_switch_vip
    comm_print_note "配置patroni环境变量"
    patroni_set_env
    comm_print_note "启动patroni并查看状态"
    systemctl start patroni
    # 查看PG是否正常启动
    ${current_dir}/deploy_pg.sh isrunning
    comm_print_note "查看patroni状态"
    patroni_status_print
    comm_print_note "patroni安装完成"
}

# 卸载patrino
function uninstall_patroni(){
    comm_print_note "进行卸载patroni"
    if [ $sys_type = "yum" ];then
      yum remove patroni -y 
    else 
      apt-get remove patroni
    fi
    rm -rf ${pg_dir}/patroni /etc/patroni 
    comm_print_note "删除patroni环境变量..."
    sed -i '/^export PATRONICTL_CONFIG_FILE=.*/d' /home/postgres/.bash_profile
}

# 卸载watchdog
function uninstall_watchdog(){
  modprobe -r softdog
  if [ $sys_type = "yum" ];then
    yum remove watchdog -y
  else 
    apt-get remove watchdog
  fi
}

# 使用说明
usage () {
        cat <<EOF
Usage: $0 [OPTIONS]
  deploy_patroni         部署patroni,会同步去初始化pg
  uninstall              卸载patroni
  status                 查看patroni状态
EOF
exit
}

# main 入口
command="${1}"
case "${command}" in
    "deploy_patroni" )
        deploy_patroni
    ;;
    "uninstall" )
      uninstall_patroni
      uninstall_watchdog
    ;;
    "status" )  
	    patroni_status_print
    ;;
    * )
        usage
    ;;
esac
